package cn.airfei.aircore.core.utils;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Map;

/**
 * @description:
 * @author: air
 * @create: 2019-06-20 13:47
 **/
public class EncryptionUtil {
    static {
        String errorString = "Failed manually overriding key-length permissions.";
        int newMaxKeyLength;
        try {
            if ((newMaxKeyLength = Cipher.getMaxAllowedKeyLength("AES")) < 256) {
                Class c = Class.forName("javax.crypto.CryptoAllPermissionCollection");
                Constructor con = c.getDeclaredConstructor();
                con.setAccessible(true);
                Object allPermissionCollection = con.newInstance();
                Field f = c.getDeclaredField("all_allowed");
                f.setAccessible(true);
                f.setBoolean(allPermissionCollection, true);
                c = Class.forName("javax.crypto.CryptoPermissions");
                con = c.getDeclaredConstructor();
                con.setAccessible(true);
                Object allPermissions = con.newInstance();
                f = c.getDeclaredField("perms");
                f.setAccessible(true);
                ((Map) f.get(allPermissions)).put("*", allPermissionCollection);
                c = Class.forName("javax.crypto.JceSecurityManager");
                f = c.getDeclaredField("defaultPolicy");
                f.setAccessible(true);
                Field mf = Field.class.getDeclaredField("modifiers");
                mf.setAccessible(true);
                mf.setInt(f, f.getModifiers() & ~Modifier.FINAL);
                f.set(null, allPermissions);
                newMaxKeyLength = Cipher.getMaxAllowedKeyLength("AES");
            }
        } catch (Exception e) {
            throw new RuntimeException(errorString, e);
        }
        if (newMaxKeyLength < 256)
            throw new RuntimeException(errorString); // hack failed
    }


    /**
     * @param data 要加密的字符串
     * @param key  私钥:
     *             AES固定格式为128/192/256 bits.即:16/24/32bytes。
     *             DES固定格式为128bits，即8bytes。
     * @param iv   初始化向量参数
     *             AES 为16bytes.
     *             DES 为8bytes.
     * @return
     * @throws Exception
     */
    public static String encrypt(String data, String key, String iv) throws Exception {
        String spec;
        if (iv.length() > 8 || key.length() > 8) {
            spec = "AES";
        } else {
            spec = "DES";
        }

        /**
         *
         * AES和DES 一共有4种工作模式:
         *     1.电子密码本模式(ECB) -- 缺点是相同的明文加密成相同的密文，明文的规律带到密文。
         *     2.加密分组链接模式(CBC)
         *     3.加密反馈模式(CFB)
         *     4.输出反馈模式(OFB)四种模式
         *
         * PKCS5Padding: 填充方式
         *
         * 加密方式/工作模式/填充方式
         * DES/CBC/PKCS5Padding
         */
        Cipher cipher;
        if (spec.equals("AES")) {
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        } else {
            cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        }

        byte[] dataBytes = data.getBytes();

        //两个参数，第一个为私钥字节数组， 第二个为加密方式 AES或者DES
        SecretKeySpec keyspec = new SecretKeySpec(key.getBytes("utf-8"), spec);
        IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());

        /**
         * 初始化，此方法可以采用三种方式，按服务器要求来添加。
         * (1)无第三个参数 --> iv
         * (2)第三个参数为SecureRandom random = new SecureRandom();中random对象，随机数。(AES不可采用这种方法)
         * (3)采用此代码中的IVParameterSpec --> 指定好了的
         *
         * 解密使用 DECRYPT_MODE 方式
         */
        cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
        byte[] encrypted = cipher.doFinal(dataBytes);

        return new Base64().encodeToString(encrypted);

    }

    /**
     * 解密
     *
     * @param dataEncode
     * @param key
     * @param iv
     * @return
     * @throws Exception
     */
    public static String desEncrypt(String dataEncode, String key, String iv) throws Exception {
        String spec;
        if (iv.length() > 8 || key.length() > 8) {
            spec = "AES";
        } else {
            spec = "DES";
        }

        //先用Base64解码
        byte[] encrypted1 = new Base64().decode(dataEncode);

        Cipher cipher;
        if (spec.equals("AES")) {
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        } else {
            cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        }
        SecretKeySpec keyspec = new SecretKeySpec(key.getBytes("utf-8"), spec);
        IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());

        // 解密使用 DECRYPT_MODE 方式
        cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);

        byte[] original = cipher.doFinal(encrypted1);
        return new String(original);
    }
}
